/*@@

	Copyright (c) 2010 BlueSky LTD. All rights reserved. 

	Created_datetime : 	2010-6-21

	File Name :	base_packmgr.cpp

	Author : liusiliang (siliangliu@msn.com)

	Description : 

@@*/


#include "zlib/zlib.h"
#include "base_xpackfile.h"
#include "base/base_str.h"
#include "base/base_packmgr.h"

namespace BLK
{
	bool SortHashID(const PackIndexInfo& Item1, const PackIndexInfo& Item2)
	{
		if(Item1.uId < Item2.uId)
			return true;
		else
			return false;
	}


	int PackMgr::ms_nDirLen = 0;
	std::vector<std::string> PackMgr::ms_vFileName;

	PackMgr::PackMgr(void)
	{
		XPackFile::Initialize();
	}

	PackMgr::~PackMgr(void)
	{
		UnLoad();

		XPackFile::Terminate();
	}

	void PackMgr::UnLoad(void)
	{
		PackFileList::iterator it = m_PackFileList.begin();
		for(; it != m_PackFileList.end(); ++it)
		{
			(*it)->Close();
			SAFE_DELETE(*it);
		}
		m_PackFileList.clear();

	}

	bool PackMgr::FindElemFile(unsigned long uId, PackElemFileRef& ElemRef)
	{
		bool bFounded = false;

		PackFileList::iterator it = m_PackFileList.begin();
		for(; it != m_PackFileList.end(); ++it)
		{
			if(true == (*it)->FindElemFile(uId, ElemRef))
			{
				bFounded = true;
				break;
			}
		}

		return bFounded;
	}

	unsigned long PackMgr::FileNameToId(const char* pszFileName)
	{
		if(pszFileName && 
			pszFileName[0])
		{
			unsigned long id = 0;
			const char* ptr = pszFileName;
			int index = 0;

			while(*ptr)
			{
				if(*ptr >= 'A' && *ptr <= 'Z') 
					id = (id + (++index) * (*ptr + 'a' - 'A')) % 0x8000000b * 0xffffffef;
				else 
					id = (id + (++index) * (*ptr)) % 0x8000000b * 0xffffffef;

				ptr++;
			}

			return (id ^ 0x12345678);
		}

		return 0;
	}

	bool PackMgr::FindElemFile(
			const char* pszFileName,
			PackElemFileRef& ElemRef)
	{
		bool bFounded = false;

		if(pszFileName && 
			pszFileName[0])
		{
			// 得到路径
			char szPackName[128];
			//szPackName[0] = BLK_BACK_SLASH;
			//GetPackPath(szPackName + 1, pszFileName);
			GetPackPath(szPackName, pszFileName);
			
			unsigned long uId = FileNameToId(szPackName);
			bFounded = FindElemFile(uId, ElemRef);
		}

		return bFounded;
	}

	bool PackMgr::LoadPackList(const std::vector<std::string>& FileList)
	{
		UnLoad();

		std::vector<std::string>::const_iterator it = FileList.begin();
		for(int nIndex = 0; it != FileList.end(); ++it, ++nIndex)
		{
			if(true == IsPackExist(*it))
			{
				continue;
			}

			if(false == LoadPack((*it).c_str(), nIndex))
			{
				UnLoad();
				return false;
			}
		}

		return true;
	}

	int PackMgr::ElemFileRead(
			PackElemFileRef& ElemRef,
			void* pBuffer, 
			unsigned uSize)
	{
		int nPackIndex = ElemRef.nPackIndex;
		if(nPackIndex >= 0 && 
			nPackIndex < (int)m_PackFileList.size())
		{
			return m_PackFileList[nPackIndex]->ElemFileRead(
				ElemRef, 
				pBuffer, 
				uSize);
		}

		return 0;
	}

	void PackMgr::FileCallBack(
		const std::string& strFile, 
		void* pArgs)
	{
		if(strFile == ".." ||
			strFile == ".")
			return;

		std::string strPath = (char*)pArgs + strFile;
		char szFile[MAXPATH_];
		::memset(szFile, 0, (unsigned)(strPath.length() + 1));
		::sprintf(szFile, "%s", strPath.c_str());
		szFile[strPath.length()] = BLK_BACK_SLASH;
		szFile[strPath.length() + 1] = '\0';

		// Modified by Lulu
		// 此函数暂时没用
//		bool bExist;
// 		Directory::IsExist(strPath, bExist);
// 		if(false == bExist)
// 			return;
// 
// 		bool bIsDir;
// 		Directory::IsDirectory(
// 			strPath,
// 			bIsDir);
// 
// 		if(true == bIsDir)
// 		{
// 			Directory::IterateFiles(szFile, FileCallBack, (void*)szFile);
// 		}
// 		else
// 		{
// 			std::string strFilePath = strPath.substr(ms_nDirLen);
// 			ms_vFileName.push_back(strFilePath);
// 		}

	}

	bool PackMgr::Pack(
		const std::string& strPackFile,
		const std::string& strDir)
	{
		std::string sPackFile = strPackFile;
		std::string sDir = strDir;

		StringFixSlash(sPackFile);
		StringFixSlash(sDir);

		// Modified by Lulu
		if(sDir[sDir.size() - 1] != BLK_BACK_SLASH)
			sDir += BLK_BACK_SLASH;

		ms_nDirLen = (int)sDir.size() + 1;
		char* szDir = new char[ms_nDirLen];
		::memset(szDir, 0, ms_nDirLen);
		::strncpy(szDir, sDir.c_str(), (unsigned)sDir.size());

		if(false == IsDirExist(szDir))
			return false;

		// 遍历目录，把文件加入列表ms_vFileName
		MakeFileList(szDir);

		PackMgr::Pack(
			sPackFile, 
			sDir, 
			ms_vFileName);

		SAFE_DELETE_ARRAY(szDir);

		return true;
	}

	bool PackMgr::Pack(
		const std::string& strPackFileName,
		const std::string& strWorkingPath,
		const std::vector<std::string>& vFileList)
	{
		if(strPackFileName.empty() ||
			vFileList.empty())
			return false;

		// 路径文件名处理
		std::string PackPath = strWorkingPath;
		StringFixSlash(PackPath);

		if(false == IsDirExist(PackPath))
			return false;

		if(PackPath[PackPath.size() - 1] != BLK_BACK_SLASH)
			PackPath += BLK_BACK_SLASH;

		std::string PackName = strPackFileName;
		StringFixSlash(PackName);
		StringCreateDir(PackName.c_str());

		// 打开包文件
		std::ofstream OutFile;
		OutFile.open(
			PackName.c_str(), 
			std::ios_base::out | std::ios_base::binary);
		if(!OutFile.good())
			return false;

		PackFileHeader head;
		::memset(&head, 0, sizeof(head));
		*(int*)(&head.cSignature) = XPACKFILE_SIGNATURE_FLAG;
		OutFile.seekp(sizeof(head), std::ios_base::beg);
		head.uDataOffset = sizeof(head);

		//	除去文件名列表中的相同名字，加入vUniqueFileNameList中
		int nCount = 0;
		std::vector<std::string> vUniqueFileNameList;
		std::vector<std::string>::const_iterator it = vFileList.begin();
		for(; it != vFileList.end(); ++it)
		{
			nCount = std::count(
				vUniqueFileNameList.begin(), 
				vUniqueFileNameList.end(), 
				*it);

			if(nCount > 0)
				continue;
			else
				vUniqueFileNameList.push_back(*it);
		}

		//	按文件路径名的ID排序
		std::vector<PackIndexInfo> vFileInfo;
		std::map<unsigned long, std::string> IdNameMap;
		it = vUniqueFileNameList.begin();
		for(; it != vUniqueFileNameList.end(); ++it)
		{
			std::string FileName = *it;
			StringFixSlash(FileName);

			PackIndexInfo FileInfo;
			FileInfo.uId = FileNameToId(FileName.c_str());
			vFileInfo.push_back(FileInfo);

			IdNameMap[FileInfo.uId] = FileName;
		}		
		std::sort(vFileInfo.begin(), vFileInfo.end(), SortHashID);

		// 打包文件
		std::vector<PackIndexInfo> FileInfoList;
		unsigned long ulFileInfoOffset = sizeof(head);

		// 压缩写入文件数据块
		std::vector<PackIndexInfo>::iterator InfoIt = vFileInfo.begin();
		for(; InfoIt != vFileInfo.end(); ++InfoIt)
		{
			unsigned uID = (*InfoIt).uId;
			std::string FileName = IdNameMap[uID];
			std::string FilePathName = PackPath + FileName;
			StringFixSlash(FilePathName);
			
			std::ifstream ifStream;
			ifStream.open(
				FilePathName.c_str(), 
				std::ios_base::in | std::ios_base::binary);
			if(!ifStream.good())
				return false;

			ifStream.seekg(0, std::ios_base::end);
			unsigned long ulFileSize = ifStream.tellg();
			ifStream.seekg(0, std::ios_base::beg);

			// 打包
			char* pFileBuf = new char[ulFileSize];
			if(!pFileBuf)
				return false;

			ifStream.read(pFileBuf, ulFileSize);
			if(ulFileSize != ifStream.gcount())
			{
				SAFE_DELETE_ARRAY(pFileBuf);
				return false;
			}

			PackIndexInfo FileInfo;
			FileInfo.uId = uID;
			FileInfo.lSize = ulFileSize;
			::memset(FileInfo.szName, 0, MAXPATH_);
			::strncpy(FileInfo.szName, FileName.c_str(), MAXPATH_);
			unsigned ulCompressSize = PackElemFile(
				pFileBuf, 
				OutFile, 
				FileInfo,
				ulFileInfoOffset);

			if(0 == ulCompressSize)
				return false;

			head.uCount++;
			FileInfoList.push_back(FileInfo);

			SAFE_DELETE_ARRAY(pFileBuf);
			ifStream.close();
		}

		// 写入文件信息
		head.uIndexTableOffset = ulFileInfoOffset;
		InfoIt = FileInfoList.begin();
		for(; InfoIt != FileInfoList.end(); ++InfoIt)
		{
			OutFile.write((char*)(&(*InfoIt)), sizeof(PackIndexInfo));
		}

		// 写入文件头
		OutFile.seekp(0, std::ios_base::beg);
		OutFile.write((char*)(&head), sizeof(head));
		OutFile.close();

		return true;
	}

	bool PackMgr::UnPack(
		const std::string& FileName, 
		const std::string& DestPath,
		bool bOverwrite /*= false*/)
	{
		if(FileName.empty() ||
			DestPath.empty())
			return false;

		std::string PackName = FileName;
		std::string DirName = DestPath;

		StringFixSlash(PackName);
		StringFixSlash(DirName);
		if(DirName[DirName.size() - 1] != BLK_BACK_SLASH)
			DirName += BLK_BACK_SLASH;

		std::ifstream PakFile;
		PakFile.open(
			PackName.c_str(), 
			std::ios_base::in | std::ios_base::binary);

		if(!PakFile.good())
			return false;

		// 读取包文件头并检查有效性
		PackFileHeader head;
		unsigned nHeadLen = sizeof(PackFileHeader);
		PakFile.seekg(0, std::ios_base::beg);
		PakFile.read((char*)(&head), nHeadLen);

		if(*(int*)(&head.cSignature) != XPACKFILE_SIGNATURE_FLAG ||
			head.uCount <= 0 || 
			head.uDataOffset < nHeadLen ||
			head.uIndexTableOffset < head.uDataOffset ||
			nHeadLen != PakFile.gcount())
			return false;

		// 读取文件信息
		PackIndexInfo* pFileInfo = new PackIndexInfo[head.uCount];
		if(!pFileInfo)
			return false;

		unsigned uInfoSize = head.uCount * sizeof(PackIndexInfo);
		PakFile.seekg(head.uIndexTableOffset, std::ios_base::beg);
		PakFile.read((char*)pFileInfo, uInfoSize);

		if(uInfoSize != PakFile.gcount())
		{
			SAFE_DELETE_ARRAY(pFileInfo);
			return false;
		}

		for(int nBlockIndex = 0; nBlockIndex < (int)head.uCount; ++nBlockIndex)
		{
			// 读取解压生成文件
			PackIndexInfo TempFileInfo = pFileInfo[nBlockIndex];
			
			// 打开输出文件
			char FilePathName[MAXPATH_];
			::memset(FilePathName, 0, MAXPATH_);
			::sprintf(FilePathName, "%s%s", DirName.c_str(), TempFileInfo.szName);

			// 创建路径中的目录
			StringCreateDir(FilePathName);

			// 文件存在的话是否要覆盖
			if(true == IsDirExist(FilePathName) && 
				false == bOverwrite)
				continue;

			std::ofstream OutFile;
			OutFile.open(
				FilePathName, 
				std::ios_base::out | std::ios_base::binary);
			if(!OutFile.good())
				goto _errexit;

			int nCompressSize = TempFileInfo.lCompressSizeFlag & (~TYPE_FILTER);
			if(TempFileInfo.uOffset + nCompressSize > head.uIndexTableOffset)
				goto _errexit;

			PakFile.seekg(TempFileInfo.uOffset, std::ios_base::beg);

			// 解压文件
			if(false == UnPackElemFile(
				PakFile, 
				OutFile,
				TempFileInfo))
			{
				goto _errexit;
			}

			OutFile.close();
		}

		PakFile.close();
		SAFE_DELETE_ARRAY(pFileInfo);
		return true;

	_errexit:
		PakFile.close();
		SAFE_DELETE_ARRAY(pFileInfo);
		return false;
	}


	/*
	 *	功能:	打开一个包文件
	 *	参数:	文件名
	 *	返回:	是否打开成功
	 */
	bool PackMgr::LoadPack(
		const char* szFile,
		int nIndex)
	{
		XPackFile* pPackFile = new XPackFile;
		if(!pPackFile)
			return false;

		if(true == pPackFile->Open(szFile, nIndex))
		{
			m_PackFileList.push_back(pPackFile);
		}
		else
		{
			pPackFile->Close();
			SAFE_DELETE(pPackFile);
			return false;
		}

		return true;
	}


	/*
	 *	功能:	取得文件在压缩包中的路径名
	 *	参数:	lpPathName	路径名
	 *			lpFileName	文件名
	 */
	void PackMgr::GetPackPath(
		char* lpPathName, 
		const char* lpFileName)
	{
		// 文件带有部分路径
		if(lpFileName[0] == BLK_SLASH || 
			lpFileName[0] == BLK_BACK_SLASH)
		{
			::strcpy(lpPathName, lpFileName + 1);
		}
		else
		{
			::strcpy(lpPathName, lpFileName);
		}

		// 全部转换为小写字母
		BLK::StringLower(lpPathName);

	}

	/*
	 *	功能:	包是否存在
	 *	参数:	strFileName	包文件名
	 *	返回:	是否存在此名字的包
	 */
	bool PackMgr::IsPackExist(const std::string& strFileName)
	{
		PackFileList::iterator it = m_PackFileList.begin();
		for(; it != m_PackFileList.end(); ++it)
		{
			std::string tempName = (*it)->GetFileName();
			if(tempName == strFileName)
				return true;
		}

		return false;
	}

	bool PackMgr::IsDirExist(const std::string& strDir)
	{
#ifdef WIN32
		WIN32_FIND_DATA data;
		::memset(&data, 0, sizeof(WIN32_FIND_DATA));

		if(::FindFirstFile(strDir.c_str(), &data) == INVALID_HANDLE_VALUE)
		{
			return false;
		}
		else
		{
			if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				return true;
			else
				return false;
		}
#endif
	}

	void PackMgr::MakeFileList(const std::string& strDir)
	{
#ifdef WIN32
		HANDLE hFind = INVALID_HANDLE_VALUE;
		WIN32_FIND_DATA FindFileData;
		DWORD dwError;

		std::string strTempDir = strDir;
		strTempDir += "/*";

		// Find the first file in the directory.
		hFind = ::FindFirstFile(strTempDir.c_str(), &FindFileData);
		if(hFind == INVALID_HANDLE_VALUE) 
		{
			//printf("Invalid file handle. Error is %u.\n", ::GetLastError());
		} 
		else 
		{
			//printf("First file name is: %s\n", FindFileData.cFileName);

			// List all the other files in the directory.
			while (FindNextFile(hFind, &FindFileData) != 0) 
			{
				std::string strFile = FindFileData.cFileName;
				if(strFile == ".." ||
					strFile == ".")
					continue;

				if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					strTempDir = strDir;
					strTempDir += "/";
					strTempDir += FindFileData.cFileName;
					std::string strTemp = strTempDir.substr(ms_nDirLen);
					//printf("\n**Next Dir name is: %s\n", strTemp.c_str());

					MakeFileList(strTempDir.c_str());
				}
				else
				{
					//printf("File name is: %s\n", FindFileData.cFileName);
				}
			}

			dwError = ::GetLastError();
			::FindClose(hFind);
			if(dwError != ERROR_NO_MORE_FILES) 
			{
				//::printf("FindNextFile error. Error is %u.\n", dwError);
				return;
			}
		}
		return;
#endif
	}

	/*
	 *	功能:	打包单个文件
	 *	参数:	pFileBuf			输入文件缓存
	 *			OutFile				输出文件流
	 *			FileInfo			记录文件信息
	 *			ulFileDataOffset	打包信息位置
	 *	返回:	压缩后的大小
	 */
	unsigned PackMgr::PackElemFile(
		const void*			pFileBuf,
		std::ofstream&		OutFile,
		PackIndexInfo&		FileInfo,
		unsigned long&		ulFileInfoOffset)
	{
		if(!pFileBuf)
			return 0;

		// 内容太小时压缩大小可能大于被压缩的大小
		unsigned long ulCompressSize = FileInfo.lSize + EXTEND_COMPRESS_SIZE;
		char* pCompressBuffer = new char[ulCompressSize];
		if(!pCompressBuffer)
		{
			return 0;
		}

		//	根据文件名判断是否需要压缩
		PackCompressMethod eMethod = GetFileCompressType(FileInfo.szName);
		switch(eMethod)
		{
		case TYPE_UCL:
			break;
		case TYPE_BZIP2:
			break;
		case TYPE_ZLIB:
			{
				// zlib压缩
				if(Z_OK != compress(
					(Bytef*)pCompressBuffer, 
					&ulCompressSize, 
					(Bytef*)pFileBuf, 
					FileInfo.lSize))
					goto _errexit;

				OutFile.write(pCompressBuffer, ulCompressSize);
			}
			break;
		case TYPE_NONE:
			{
				// 不压缩
				ulCompressSize = FileInfo.lSize;
				OutFile.write(pCompressBuffer, FileInfo.lSize);
			}
			break;
		default:
			break;
		}

		// 记录文件信息
		FileInfo.lCompressSizeFlag = eMethod + ulCompressSize;
		FileInfo.uOffset = ulFileInfoOffset;
		ulFileInfoOffset += ulCompressSize;

		SAFE_DELETE_ARRAY(pCompressBuffer);
		return ulCompressSize;

	_errexit:
		SAFE_DELETE_ARRAY(pCompressBuffer);
		return 0;
	}

	/*
	 *	功能:	解包一个文件
	 *	参数:	InFile		输入文件流
	 *			OutFile		输出文件流
	 *			FileInfo	文件信息
	 *	返回:	是否解包成功
	 */
	bool PackMgr::UnPackElemFile(
		std::ifstream& InFile,
		std::ofstream& OutFile,
		const PackIndexInfo& FileInfo)
	{
		int nCompressSize = FileInfo.lCompressSizeFlag & (~TYPE_FILTER);
		char* pCompBuffer = new char[nCompressSize];
		if(!pCompBuffer)
			return false;

		InFile.read(pCompBuffer, nCompressSize);
		if(nCompressSize != InFile.gcount())
			goto _errexit;
{
		unsigned long ulType = FileInfo.lCompressSizeFlag & TYPE_FILTER;
		switch(ulType)
		{
		case TYPE_NONE:
			{
				//	读取未压缩数据并写入文件
				if(nCompressSize != FileInfo.lSize)
					goto _errexit;

				OutFile.write(pCompBuffer, nCompressSize);
			}
			break;
		case TYPE_ZLIB:
			{
				// 读取使用GZIP压缩数据并写入文件
				unsigned long ulDecompSize = FileInfo.lSize;
				char* pBuffer = new char[ulDecompSize];
				if(!pBuffer)
					goto _errexit;

				if(Z_OK != uncompress(
					(Bytef*)pBuffer, 
					&ulDecompSize,
					(Bytef*)pCompBuffer, 
					nCompressSize))
				{
					SAFE_DELETE_ARRAY(pBuffer);
					goto _errexit;
				}

				OutFile.write(pBuffer, ulDecompSize);
				SAFE_DELETE_ARRAY(pBuffer);
			}
			break;
		default:
			break;
		}

		OutFile.close();
		SAFE_DELETE_ARRAY(pCompBuffer);
		return true;
}
	_errexit:
		OutFile.close();
		SAFE_DELETE_ARRAY(pCompBuffer);
		return false;
	}

	/*
	 *	功能:	根据文件名返回压缩类型
	 *	参数:	FileName	文件名
	 *	返回:	压缩类型
	 */
	PackCompressMethod PackMgr::GetFileCompressType(const std::string& FileName)
	{
		// 先都用ZLIB压缩
		return TYPE_ZLIB;
	}

};
